#ifndef __AVR_ATmega16__
#define __AVR_ATmega16__
#endif
#ifndef F_CPU
#define F_CPU 8000000
#endif
//OSCCAL A3A49D9E
#define refresh(x) (F_CPU / 256 / (x)) ;//dalam Hz
#define FAN_OFFSET 75
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <math.h>
#include <avr/io.h>
#include <avr/eeprom.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include <avr/boot.h>
#include <avr/wdt.h>
#include "pid.h"

#define bv(x) _BV(x)
#define char_o 29
#define char_O 18
#define char_k 117
#define char_l 35
#define char_min 253
#define char_E 56
#define char_F 120
#define char_S 152

//#define SEG_LEARNING_MODE

static volatile unsigned char status;
/*
 * bit 0 : task
 * bit 1 : task
 * bit 2 : task
 * bit 3 : Button encoder has pressed=1, float=0
 * bit 4 : Button is being pressed
 * bit 5 : Encoder is moving
 * bit 6 : FAN has stopped by external event //not work~noisy
 * bit 7 : Sensor heater disconnected
 */
#define TASK_MASK 0x7
#define TASK_SWITCH(x) (status = (status & ~TASK_MASK) | (x))

static unsigned char angka[10] PROGMEM = {18, 215, 49, 145, 212, 152, 24, 211, 16, 144};

static volatile unsigned char a_rat; //symbol yang akan ditampilkan di digit ratusan 7seg atas
static volatile unsigned char a_pul; //symbol yang akan ditampilkan di digit puluhan 7seg atas
static volatile unsigned char a_sat; //symbol yang akan ditampilkan di digit satuan 7seg atas
static volatile unsigned char b_rat; //symbol yang akan ditampilkan di digit ratusan 7seg bawah
static volatile unsigned char b_pul; //symbol yang akan ditampilkan di digit puluhan 7seg bawah
static volatile unsigned char b_sat; //symbol yang akan ditampilkan di digit satuan 7seg bawah

volatile uint16_t PV; //Disimpan 4*TEMP utk memudahkan rotary encoder
volatile int16_t SV = 200; //100derajat
volatile uint8_t FAN_PWM = 50;
volatile uint8_t EXT; //Disimpan 1*TEMP
volatile uint8_t PULSE;

static unsigned char send_spi(unsigned char);
int8_t get_encoder(void);

#ifdef DEBUG_MODE
static int uart_putchar(char c, FILE *stream);
static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);

static int uart_putchar(char c, FILE *stream) {
    if (c == '\n')
        uart_putchar('\r', stream);
    loop_until_bit_is_set(UCSRA, UDRE);
    UDR = c;
    return 0;
}
#warning Debug MODE ON!!
#endif

int main(void) {
#ifdef DEBUG_MODE
    stdout = &mystdout;
#endif
    uint16_t temp = 0;
    cli();
    OSCCAL = 0x9E;
    /*INISIALISASI PORT*/
    SFIOR &= ~_BV(PUD); //enable pull up; dipakai oleh int1
    //inisialisasi portA
    DDRA = 0xFF ^ bv(DDA0); // 7seg scanline utk baris atas (A4,A5,A6); cable select(CS) untuk MAX6675 ext (A3)
    PORTA |= _BV(PORTA3) | bv(PORTA0); //mereset MAX6675 ext

    //inisialisasi portB
    /*
     The setup of the OC0 should be performed before setting the Data Direction Register for the port
    pin to output. The easiest way of setting the OC0 value is to use the Force Output Compare
    (FOC0) strobe bits in Normal mode. The OC0 Register keeps its value even when changing
    between waveform generation modes.
     */
    DDRB = _BV(DDB7) | _BV(DDB5) | _BV(DDB4); // SCK,MOSI,SS output
    PORTB = _BV(PORTB6) | _BV(PORTB0) | _BV(PB4); //pull-up untuk port MISO;pull-up untuk T0 (counter timer0); reset MAX6675 internal
#ifdef DEBUG_MODE
    PORTB |= bv(PB1);
#endif
    //inisialisasi portC
    DDRC = 0xFF; //output buat 7seg
    PORTC = 0xFF; //semua segmen menyala
    //inisialisasi portD
    DDRD = _BV(DDD7) | _BV(DDD6) | _BV(DDD3); //7 seg scanline (D7,D6,D3)
    //D2,D1,D0 input rotary encoder
    PORTD = _BV(PD0) | _BV(PD1) | _BV(PD2); //pull-up rotary encoder+switch
    MCUCR |= bv(ISC01) | bv(ISC00);
    GICR |= bv(INT0);
    
    /*INISIALISASI MODUL*/
    //seven segment cc, all on
    PORTC = 0;
    PORTD &= ~_BV(PD7) & ~_BV(PD6) & ~_BV(PD3);
    PORTA &= ~_BV(PA6) & ~_BV(PA5) & ~_BV(PA4);
    _delay_ms(2000);
    PORTD |= _BV(PD7) | _BV(PD6) | _BV(PD3);
    PORTA |= _BV(PA6) | _BV(PA5) | _BV(PA4);
#ifdef DEBUG_MODE
    if (UCSRA & bv(RXC)) {
        char in = UDR;
        if (in == 0x1b) {
            void (*bootload) (void) = (void *) 0x1c6b; //0x38e2;
            bootload();
        }
    }
#else
    if (!(PINA & bv(PORTA0))) {
        void (*bootload) (void) = (void *) 0x1c6b; //0x38e2;
        bootload();
    }
#endif 
    //Timer 0 sebagai triac driver
    PORTB &= ~_BV(PB3); // matikan triac
    OCR0 = 0;
    TCCR0 = _BV(CS02) | _BV(CS01) | bv(WGM01);
    TIMSK |= bv(OCIE0);
    DDRB |= _BV(PB3); // oc0 output

    //PWM fan 10bit phase correct
    TCCR1A = bv(COM1A1) | bv(WGM10) | bv(WGM10); //output di OC1A
    TCCR1B = bv(CS10); //prescaler 1
    OCR1A = 1 * (FAN_PWM + FAN_OFFSET);
    DDRD |= bv(PD5); // ganti ke PD5
    TIMSK |= bv(TOIE1);

    //refresh timer 2 untuk 7seg
    ASSR = 0;
    TCCR2 = _BV(WGM21) | _BV(CS22) | _BV(CS21); //WGM21:CTC Mode; CS22|CS21:prescaler 256
    OCR2 = refresh(333); //333 Hz switching 7seg ~ 60fps atau 144Hz ~ 24fps
    TIMSK |= _BV(OCIE2);

    //SPI
    SPCR = _BV(MSTR) | _BV(SPE) | _BV(SPR1); //SPI master, interrupt enable, prescaler 4
    sei(); //enable interrupt

#ifdef DEBUG_MODE
    //UBRRH = 0;
    UBRRL = 12; //38400 baud
    UCSRB |= bv(RXEN) | bv(TXEN); // | bv(RXCIE);
    printf_P(PSTR("Debug Mode ON!!\n"));
#endif

    PORTA &= ~bv(PA6); //nyalakan 7seg pertama
    TASK_SWITCH(1);
    Initialize();
    OCR0 = 0;
    SetTunings(3, 2, 11);
    SetOutputLimits(0, 140);
    uint16_t count1 = 0, count2 = 0;
    while (1) {
        if (count2 > 500) {
            unsigned char data_h;
            unsigned char data_l;
            //cek suhu internal
            PORTB &= ~_BV(PB4); //stop converting int
            data_h = send_spi(0);
            data_l = send_spi(0);
            PORTB |= _BV(PB4); //continue converting int
            if (data_l & _BV(2)) { // D2 high?
                //Open Thermocouple => shutdown heat
                PULSE = 0;
                PORTB &= ~bv(PB3);
                status |= bv(7);
            } else {
                status &= ~bv(7);
            }
            temp = (data_h << 5); //TEMP = RAW
            temp += (data_l >> 3); // max 0xfff = 4095 => (4095/4)^C = 1023,75^C
            PV = temp;

            //cek suhu eksternal
            PORTA &= ~_BV(PA3); //stop converting int
            data_h = send_spi(0);
            data_l = send_spi(0);
            PORTA |= _BV(PA3); //continue converting int
            if ((data_l & bv(2))) {
                //printf_P(PSTR("Open TC ext\n"));
            } else {
                temp = (data_h << 3); //TEMP = RAW / 4
                temp += (data_l >> 5); // max 0x3ff = 1023 => 1023^C
                EXT = temp;
            }

            int16_t pidval = Compute(); //pid_Controller(SV, PV, &pid);
            if (pidval > 8) {
                PULSE = (uint8_t) (pidval / 8);
            } else {
                if (SV > 400 && FAN_PWM > 50) {
                    PULSE += 1;
                } else {
                    PULSE = 0;
                }
            }
#ifdef DEBUG_MODE
            //printf_P(PSTR("Suhu :%d C\t%d C \n"), (PV / 4), EXT);
            printf_P(PSTR("FAN:%u SV: %u PV:%u PID:%u PIDc:%d PULSE:%d STATUS:%u kp:%u ki:%u kd:%u err:%d Ite:%d\n"), FAN_PWM, SV, PV, OCR0, pidval, PULSE, status, kp, ki, kd,error,ITerm);
#endif
            count2 = 0;
        }
#ifdef DEBUG_MODE
        if (UCSRA & bv(RXC)) {
            char in = UDR;
            if (in == 'w') {
                kp += 1;
                printf_P(PSTR("kp = %u\n"), kp);
            } else if (in == 's') {
                kp -= 1;
            } else if (in == 'q') {
                ki += 1;
            } else if (in == 'a') {
                ki -= 1;
            } else if (in == 0x1b) {
                OCR0 = 0;
                DDRD &= ~_BV(PD7) & ~_BV(PD6) & ~_BV(PD3);
                DDRA &= ~_BV(PA6) & ~_BV(PA5) & ~_BV(PA4);
                cli();
                PORTB &= ~_BV(PB3);
                void (*bootload) (void) = (void *) 0x1c6b; //0x38e2;
                bootload();
            } else if (in == 'e') {
                kd += 1;
            } else if (in == 'd') {
                kd -= 1;
            } else if (in == 'z') {
                FAN_PWM += 10;
                OCR1A = (FAN_PWM + FAN_OFFSET);
            } else if (in == 'x') {
                FAN_PWM -= 10;
                OCR1A = (FAN_PWM + FAN_OFFSET);
            } else if (in == 'p') {
                OCR0++;
            } else if (in == 'o') {
                OCR0--;

            } else if (in == 'l') {

            } else if (in == 'n') {
                PORTB |= bv(PB3);
            } else if (in == 'm') {
                PORTB &= ~bv(PB3);
            } else if (in == 'b') {
                PORTB |= bv(PB3);
                _delay_us(100);
                PORTB &= ~bv(PB3);
            } else if (in == 'u') {
                //DDRD |= bv(PD4);
                //TCCR1A = bv(COM1A1) | _BV(COM1B1) | bv(WGM12) | bv(WGM10);
            } else if (in == 'i') {
                //TCCR1A = 0;//&= ~bv(COM1B1) & ~bv(COM1B0);
                //PORTD ^= bv(PD4);
                DDRD |= bv(PD5);
            }
        }
        if (PINB & bv(PB1)) {
            if (status & bv(4)) {
                status &= ~bv(4);
                status |= bv(3);
            }
        } else {
            //if(!(status & bv(4))){
            status |= bv(4);
        }
#else
        if ((PIND & bv(PD1))) {
            if (status & bv(4)) {
                status &= ~bv(4);
                status |= bv(3);
            }
        } else {
            status |= bv(4);
        }
#endif

        //#define SEG_LEARNING_MODE
#ifndef SEG_LEARNING_MODE
        uint8_t task = (status & TASK_MASK);
        switch (task) {
            case 1:
                if (status & bv(7)) {
                    a_rat = char_S;
                    a_pul = char_min;
                    a_sat = char_E;
                } else if (status & bv(6)) {
                    a_rat = char_F;
                    a_pul = char_min;
                    a_sat = char_E;
                } else {
                    temp = PV / 4;
                    a_rat = pgm_read_byte(&angka[temp / 100]);
                    a_pul = pgm_read_byte(&angka[(temp / 10) % 10]);
                    a_sat = pgm_read_byte(&angka[temp % 10]);
                }
                temp = EXT;
                b_rat = pgm_read_byte(&angka[temp / 100]);
                b_pul = pgm_read_byte(&angka[(temp / 10) % 10]);
                b_sat = pgm_read_byte(&angka[temp % 10]);
                if (status & bv(3)) {
                    TASK_SWITCH(3);
                    status &= ~bv(3);
                }
                break;
            case 2:
                if (status & bv(5)) {
                    count1 = 0;
                    status &= ~bv(5);
                } else {
                    status &= ~bv(3) & ~bv(5);
                    count1++;
                }
                temp = PV / 4;
                a_rat = pgm_read_byte(&angka[temp / 100]);
                a_pul = pgm_read_byte(&angka[(temp / 10) % 10]);
                a_sat = pgm_read_byte(&angka[temp % 10]);

                temp = SV / 4;
                b_rat = pgm_read_byte(&angka[temp / 100]);
                b_pul = pgm_read_byte(&angka[(temp / 10) % 10]);
                b_sat = pgm_read_byte(&angka[temp % 10]);
                if (count1 > 1000) {
                    TASK_SWITCH(1);
                    count1 = 0;
                }
                break;
            case 3:
                if (status & bv(5)) {
                    count1 = 0;
                    status &= ~bv(5);
                } else {
                    status &= ~bv(5);
                    count1++;
                }
                a_rat = char_F;
                a_pul = char_min;
                a_sat = char_S;
                temp = FAN_PWM;
                b_rat = pgm_read_byte(&angka[temp / 100]);
                b_pul = pgm_read_byte(&angka[(temp / 10) % 10]);
                b_sat = pgm_read_byte(&angka[temp % 10]);
                if ((status & bv(3)) || count1 > 3000) {
                    TASK_SWITCH(1);
                    status &= ~bv(3) & ~bv(5);
                    count1 = 0;
                }
                break;
        }
#else
        uint8_t i = 1;
        do {
            b_sat = i;
            b_rat = i;
            b_pul = i;
            a_sat = i;
            a_rat = ~i;
            a_pul = ~i;

            i <<= 1;
            _delay_ms(500);
        } while (i > 0);
        _delay_ms(1000);
#endif
        count2++;
        _delay_ms(1);
    }

    return 0;
}

static unsigned char send_spi(unsigned char data) {
    SPDR = data;
    do {
    } while (!(SPSR & _BV(SPIF)));
    return SPDR;
}

ISR(TIMER0_COMP_vect) {
    /*PID untuk temperature*/
    if ((PULSE > 0) && !(status & bv(7))) {
        PORTB |= bv(PB3);
        _delay_us(300); // ini darimana?
        PORTB &= ~bv(PB3);
        PULSE--;
    }
}

ISR(TIMER1_OVF_vect) {
    /*8kHz FAN check*/
    if (PIND & bv(PD4)) {
        status &= ~bv(6);
    } else {
        status |= bv(6);
    }
}

ISR(INT0_vect) {
    uint8_t task = status & TASK_MASK;
    int8_t enc;
    int16_t temp;
    status |= bv(5);
#ifdef DEBUG_MODE
    if (PINA & bv(PINA0)) {
        enc = 0;
    } else {
        enc = 1;
    }
#else
    if (PIND & bv(PIND0)) {
        enc = 0;
    } else {
        enc = 1;
    }
#endif
    switch (task) {
        case 1:
            TASK_SWITCH(2);
            break;
        case 2:
            temp = SV;
            if (status & bv(4)) {
                if (enc) {
                    temp += 4;
                    if (temp < 1200)
                        SV = temp;
                    else
                        SV = 1200;
                } else {
                    temp -= 4;
                    if (temp > 0)
                        SV = temp;
                    else
                        SV = 0;
                }
            } else {
                if (enc) {
                    temp += 40;
                    if (temp < 1200)
                        SV = temp;
                    else
                        SV = 1200;
                } else {
                    temp -= 40;
                    if (temp > 0)
                        SV = temp;
                    else
                        SV = 0;
                }
            }
            break;
        case 3:
            if (enc) {
                if (FAN_PWM < (255 - FAN_OFFSET))
                    FAN_PWM++;
                else
                    FAN_PWM = (255 - FAN_OFFSET);
            } else {
                if (FAN_PWM > 0)
                    FAN_PWM--;
                else
                    FAN_PWM = 0;
            }
            OCR1A = 1 * (FAN_PWM + FAN_OFFSET);
            break;
    }
}

ISR(TIMER2_COMP_vect) {//~=14ns
    if (!(PORTA & _BV(PA6))) {
        PORTA |= _BV(PORTA6);
        PORTA &= ~_BV(PORTA5);
        PORTC = a_pul;
    } else if (!(PORTA & _BV(PA5))) {
        PORTA |= _BV(PORTA5);
        PORTA &= ~_BV(PORTA4);
        PORTC = a_rat;
    } else if (!(PORTA & _BV(PA4))) {
        PORTA |= _BV(PORTA4);
        PORTD &= ~_BV(PORTD6);
        PORTC = b_sat;
    } else if (!(PORTD & _BV(PD6))) {
        PORTD |= _BV(PORTD6);
        PORTD &= ~_BV(PORTD7);
        PORTC = b_pul;
    } else if (!(PORTD & _BV(PD7))) {
        PORTD |= _BV(PORTD7);
        PORTD &= ~_BV(PORTD3);
        PORTC = b_rat;
    } else if (!(PORTD & _BV(PD3))) {
        PORTD |= _BV(PORTD3);
        PORTA &= ~_BV(PORTA6);
        PORTC = a_sat;
    }
}

